1. PYTHON CODING SKILLS
BASIC SKILLS
INTERMEDIATE SKILLS
ADVANCED SKILLS
2. DATA STRUCTURES & ALGORITHMS
3. DEBUGGING SKILLS
4. USING LIBRARIES WISELY
5. PEP8 & CODING FAST PRACTICES
6. OPEN SOURCE CONTRIBUTIONS & CODE REVIEWS
7. MASTER THE ART OF ASKING FOR HELP
8. PROJECT, PRACTICE, REPEAT UH
Its a container that can hold any data
'''Example problem: Find out total monthly expense given individual expense items
Sol: Store in-user expenses in some place and then use that place to do a sum of
all those individual expenses to come up with total expense'''
rent = 1220 # '=' is a assignment operator, left side is variable and right is the value stored in the variable
gas = 202.5
groceries = 305.6
total = rent + gas + groceries
print(total)
1728.1
We can most likely do any mathematical operations [like a calculator]
5 + 2
7
We can also do operations on stored variables
a = 5
b = 6
c = a * b
print(c)
30
We can use round() to control the decimal points
a = 10
b = 3
c = a / b
print(c)
3.3333333333333335
round(c, 2)
3.33
round(c, 3)
3.333
Its used to store text data
text = 'ice cream'
print(text)
ice cream
'''It stores it in a sequence of characters
0 1 2 3 4 5 6 7 8
i c e c r e a m
If I want get the character on particular index I can do it like this one on below
'''
text[0]
'i'
'''I can also give a range where we technically call it as slicing in python [start : stop]'''
text[0:3]
'ice'
text[4:9]
'cream'
We can store n number of elements in form of an array
'''Lets take a scenerio that we go to a grocery store and I need some items like
bread, nutella, maybe some crackers like hide n seek, or fruits, I can store all
this in one place in python using lists'''
items = ["bread", "nutella", "hide n seek", "fruits"]
items
['bread', 'nutella', 'hide n seek', 'fruits']
'''The way this list stored is like
0 1 2 3
bread nutella hide n seek fruits
It is similar to strings, the way it stored on memory locations
'''
items[0]
'bread'
items[2]
'hide n seek'
'''If I need chips instead of bread I can modify like this'''
items[0] = 'chips'
items
['chips', 'nutella', 'hide n seek', 'fruits']
'''To access range of elements'''
items[0:2]
['chips', 'nutella']
items[-1] # negative index '-1' means first index seen from the end
'fruits'
# We can use append() to add an element
items.append("paneer")
items
['chips', 'nutella', 'hide n seek', 'fruits', 'paneer']
'''We can also append on specific locations'''
items.insert(1, '7-up')
items
['chips', '7-up', 'nutella', 'hide n seek', 'fruits', 'paneer']
'''We can also join two lists'''
items_cleaning = ['harpic', 'lizol']
complete_items = items + items_cleaning
complete_items
['chips', '7-up', 'nutella', 'hide n seek', 'fruits', 'paneer', 'harpic', 'lizol']
'''HEADS UP: While concatenating you can add a string directly to the list, it will throw an error'''
'HEADS UP: While concatenating you can add a string directly to the list, it will throw an error'
# We can use len() to check the length of list
len(complete_items)
8
'''There should be a better way than reading the elements of the list one by one
The better way to do this is "in" operator'''
"chips" in items
True
"coke" in items
False
SAMPLE PROGRAM 1 : Write a program that asks user to enter a number. Program should then tell if number is odd or even
num = input("Enter a number: ") # Whatever the input is, it initially stored as a string, so we have to convert it to appropriate variable
Enter a number: 3
num = int(num)
if num % 2 == 0:
print("number is even")
else:
print("number is odd")
number is odd
SAMPLE PROGRAM 2 : Write a program that asks user to enter dish name and it should print which cuisine is that dish
indian = ["sambar", "curd", "parotha"]
chinese = ["noodles", "fried rice", "dim sum"]
italian = ["pasta", "pizza", "risotto"]
dish = input("Enter a dish name: ")
Enter a dish name: noodles
if dish in indian:
print("Indian cuisine")
elif dish in chinese:
print("Chinese cuisine")
elif dish in italian:
print("Italian cuisine")
else:
print("We dont sell it here -_-")
Chinese cuisine
SAMPLE PROBLEM : Store monthly expenses in a list and find out total expenses for all months
# traditional way without using for loop
exp = [23400, 25000, 21000, 31000, 29800]
total = exp[0] + exp[1] + exp[2] + exp[3] + exp[4]
print(total)
'''The problem with this way is its a freaking long code where my brain will implode when it comes to like 100 elements'''
130200
# The better way is to use for loop
total = 0 # We keep it as 0 since its gonna get added when it runs on loop
for item in exp:
total = total + item
print(total)
# If you look at the difference in code, this is far more better and brain wont implode ffs
130200
'''What if we want print numbers using range() function "range(start, stop)"'''
for i in range(1,11):
print(i)
1 2 3 4 5 6 7 8 9 10
In our monthly expense example, print month number and expense and then in the end print total expense
total = 0
for i in range(len(exp)):
print('Month:',(i+1), ' Expense:',exp[i]) # Giving 'i+1' because in range funtion we will only specify end index, if start index is not specified, it will be 0 by default, I want to start from 1
total = total + exp[i]
print('Total expense is:',total)
Month: 1 Expense: 23400 Month: 2 Expense: 25000 Month: 3 Expense: 21000 Month: 4 Expense: 31000 Month: 5 Expense: 29800 Total expense is: 130200
'''If my program goal is achieved, the for loop should be terminated
A better example, like if we lost our car key in home, we begin searching it,
We search in multiple places and once found we stop searching. Lets demonstrate'''
key_location = "chair"
locations = ["shed", "pooja room", "living room", "chair", "backyard", "closet"]
for i in locations:
if i == key_location:
print("key is found in", i)
break # terminate
else:
print("key is not found in",i)
key is not found in shed key is not found in pooja room key is not found in living room key is found in chair
'''Another useful statement is "continue", its merely the skipping iterations.
Lets take a problem, Print square of numbers between 1 to 5 except even numbers'''
for i in range(1,6):
if i % 2 == 0:
continue # if it wrong or right it doesnt care, it just print what satisfies the condition and continue running the loop by skipping the wrong ones
print(i*i)
1 9 25
# USING WHILE LOOP
i = 1
while i<=5:
print(i)
i = i + 1
1 2 3 4 5
Functions is a block of code that performs a specific task
'''Lets take washing machine as real time example, we put dirty clothes in the machine, it will wash it and give out the
clean clothes, See it in a format, Dirty clothes here acts as funtion arguments, And we do some tasks like
Function : wash_clothes
1. Add water/detergent
2. Wash clothes
3. Give out clean clothes
So the output here is clean clothes[return value]
'''
'''
SAMPLE PROBLEM : YOU ARE GIVEN TWO LISTS OF NUMBERS AND YOU NEED TO FIND
TOTAL OF EACH OF THESE LIST
'''
# traditional way
raj_exp_list = [21000, 34000, 35000]
rita_exp_list = [2000, 5000, 7000]
total = 0
for item in raj_exp_list:
total = total + item
print("Raj's total expenses:",total)
total = 0
for item in rita_exp_list:
total = total + item
print("Rita's total expenses:",total)
'''The problem with this code is we are repeating three lines of code again and again, lets see we need
to find the total expense of 10 people, then it becomes tedious right? So thats where functions are used'''
Raj's total expenses: 90000 Rita's total expenses: 14000
# Using the function
def calculate_total(exp): # def is the special keyword that tells python that we are writing function
total = 0
for item in exp:
total = total + item
return total
raj_total = calculate_total(raj_exp_list)
rita_total = calculate_total(rita_exp_list)
print("Raj's total expenses:",raj_total)
print("Rita's total expenses:",rita_total)
Raj's total expenses: 90000 Rita's total expenses: 14000
# Another example, sum of two numbers
total2 = 0 # global variable
def sum(a,b): # defining function
total1 = a + b # local variable
print("Total from inside the function:",total1)
return total # return value
n = sum(5,6) # calling function
print("Total from outside the function:",total2)
Total from inside the function: 11 Total from outside the function: 0
Dictionary allows us to store key, value pairs. It also called as Maps, Hashtables, Associate Arrays. A best real time example for dictionaries are telephone directory. Let's have a look,
d = {"raju":9876514590, "ranga":8976542310, "ravi":6309176532}
d
{'raju': 9876514590, 'ranga': 8976542310, 'ravi': 6309176532}
d["ranga"] # We can access specific key values by calling that key
8976542310
# Adding new entry in a dictionary
d["sita"] = 8797165342
d
{'raju': 9876514590,
'ranga': 8976542310,
'ravi': 6309176532,
'sita': 8797165342}
# Deleting a entry in a dictionary
del d["ranga"]
d
{'raju': 9876514590, 'ravi': 6309176532, 'sita': 8797165342}
for key in d:
print("key:",key,"value:",d[key])
key: raju value: 9876514590 key: ravi value: 6309176532 key: sita value: 8797165342
for k,v in d.items():
print("key:",k,"value:",v)
key: raju value: 9876514590 key: ravi value: 6309176532 key: sita value: 8797165342
"ravi" in d
True
"ranga" in d
False
d.clear() # clear every entries in a dictionary
d
{}
Tuple is a list of values grouped together
#For example, if we want represent geometric points in 2D plane, we will use tuple
point = (5,9)
point[0]
5
'''
It is more likely similar to list, but it has major difference.
In List, all values have similar meaning (Homogeneous)
In Tuple, all values have different meaning (Heterogeneous)
'''
point_tuple = (5,9) # 5 is X_coordinate, 9 is Y_coordinate
point_list = [5,9] # both 5 and 9 are X_coordinates
# Second difference is tuples are immutable, meaning if we try to change we cannot, but we can change in a list
The whole idea of "reuse" applies well to the programming world. Module is a way to reuse a code written by someone else.
# Let's use math module
import math
math.sqrt(16)
4.0
math.pow(2,5) # 2 is the number and 5 is the power to be mentioned
32.0
# List of all the functions in math module. Using dir() command
dir(math)
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'lcm', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'nextafter', 'perm', 'pi', 'pow', 'prod', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc', 'ulp']
math.log10(100)
2.0
math.log10(1000)
3.0
math.floor(2.3)
2
math.ceil(2.3)
3
# Let's use calendar module
import calendar
cal = calendar.month(2022,10)
print(cal)
October 2022
Mo Tu We Th Fr Sa Su
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
calendar.isleap(2022)
False
calendar.isleap(2016)
True
# List of all functions in calendar module. Using dir()
dir(calendar)
['Calendar', 'EPOCH', 'FRIDAY', 'February', 'HTMLCalendar', 'IllegalMonthError', 'IllegalWeekdayError', 'January', 'LocaleHTMLCalendar', 'LocaleTextCalendar', 'MONDAY', 'SATURDAY', 'SUNDAY', 'THURSDAY', 'TUESDAY', 'TextCalendar', 'WEDNESDAY', '_EPOCH_ORD', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_colwidth', '_locale', '_localized_day', '_localized_month', '_monthlen', '_nextmonth', '_prevmonth', '_spacing', 'c', 'calendar', 'datetime', 'day_abbr', 'day_name', 'different_locale', 'error', 'firstweekday', 'format', 'formatstring', 'isleap', 'leapdays', 'main', 'mdays', 'month', 'month_abbr', 'month_name', 'monthcalendar', 'monthrange', 'prcal', 'prmonth', 'prweek', 'repeat', 'setfirstweekday', 'sys', 'timegm', 'week', 'weekday', 'weekheader']
# We can also create our own module and use it in a program
# I have already created a module and I will just write the code in documentation
'''
def calculate_triangle_area(base,height):
return 1/2*(base*height)
def calculate_square_area(length):
return length*length
'''
import functions
functions.calculate_square_area(5)
25
# We can also use short forms for the imported modules
import functions as f
f.calculate_triangle_area(2,3)
# HEADS UP : To find a module, python will use current directory and then all directories listed in system path
3.0
# To install a python module we use certain command for that
!pip install matplotlib
'''
For Jupyter Notebook I'm mentioning "!", in general you dont need to mention it.
And you need to have a internet connection to download and install a module
'''
# To uninstall use "pip uninstall module_name"
Requirement already satisfied: matplotlib in c:\anaconda3\lib\site-packages (3.5.3) Requirement already satisfied: pillow>=6.2.0 in c:\anaconda3\lib\site-packages (from matplotlib) (9.2.0) Requirement already satisfied: python-dateutil>=2.7 in c:\anaconda3\lib\site-packages (from matplotlib) (2.8.2) Requirement already satisfied: cycler>=0.10 in c:\anaconda3\lib\site-packages (from matplotlib) (0.11.0) Requirement already satisfied: packaging>=20.0 in c:\anaconda3\lib\site-packages (from matplotlib) (21.3) Requirement already satisfied: fonttools>=4.22.0 in c:\anaconda3\lib\site-packages (from matplotlib) (4.33.3) Requirement already satisfied: pyparsing>=2.2.1 in c:\anaconda3\lib\site-packages (from matplotlib) (3.0.9) Requirement already satisfied: numpy>=1.17 in c:\anaconda3\lib\site-packages (from matplotlib) (1.22.3) Requirement already satisfied: kiwisolver>=1.0.1 in c:\anaconda3\lib\site-packages (from matplotlib) (1.4.2) Requirement already satisfied: six>=1.5 in c:\anaconda3\lib\site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
'\nFor Jupyter Notebook I\'m mentioning "!", in general you dont need to mention it.\nAnd you need to have a internet connection to download and install a module\n\n'
WHAT IS JSON? - Java Script Object Notation. JSON is a data exchange format similar to XML.
'''
JSON(less volume of data)
----
{
"name":"Siva",
"address":"Pondicherry"
"phone":"9876543210"
}
XML(more volume of data)
---
<name>Siva</name>
<address>Pondicherry</address>
<phone>9876543210</phone>
JSON is a lightweight format compared to XML
'''
# SAMPLE PROGRAM 1 - To create address book and write some records into it
book = {}
book['siva'] = {
'name': 'siva',
'address': 'Pondicherry',
'phone': 9876543210
}
book['sankar'] = {
'name': 'sankar',
'address': 'Cuddalore',
'phone': 9876543211
}
import json
s = json.dumps(book) # dumps() will take dictionary object book as an input then it is dumping it as its string. It will then convert to json format
with open("book.txt", "w") as f:
f.write(s)
We can now read this JSON data using any language that supports JSON such as Javascript, C++ etc. Hence this is called data exchange format(i.e. exchanging data from python program to javascript program)
# Read the JSON records
f = open("book.txt","r")
s2 = f.read()
s2
'{"siva": {"name": "siva", "address": "Pondicherry", "phone": 9876543210}, "sankar": {"name": "sankar", "address": "Cuddalore", "phone": 9876543211}}'
book2 = json.loads(s2) # "loads() is basically loading the string"
book2
{'siva': {'name': 'siva', 'address': 'Pondicherry', 'phone': 9876543210},
'sankar': {'name': 'sankar', 'address': 'Cuddalore', 'phone': 9876543211}}
book2['siva']
{'name': 'siva', 'address': 'Pondicherry', 'phone': 9876543210}
book2['siva']['phone']
9876543210
# To print all the records in the book2
for person in book2:
print(book2[person])
{'name': 'siva', 'address': 'Pondicherry', 'phone': 9876543210}
{'name': 'sankar', 'address': 'Cuddalore', 'phone': 9876543211}
__name__
'__main__'
'''You can see the above cell and it is that, it is entry point for any python program
It is written as
if __name__ == "__main__"
Lets see an example
'''
def calculate_area(base, height):
return 1/2*(base*height)
if __name__ == "__main__":
a = calculate_area(10, 20)
print("area: ",a)
area: 100.0
# area.py
def calculate_area(base, height):
print("__name__: ",__name__)
return 1/2*(base*height)
if __name__ == "__main__":
print("I am in area.py")
a = calculate_area(10, 20)
print("area: ",a)
I am in area.py __name__: __main__ area: 100.0
# When does __name__ is something else other than main?
import area
# I created a module named area, I will show the code in documentation
'''
def calculate_area(base, height):
print("__name__: ",__name__)
return 1/2*(base*height)
if __name__ == "__main__":
print("I am in area.py")
a = calculate_area(10, 20)
print("area: ",a)
'''
# You can show check the output on above cell
print("I am in caller.py")
area.calculate_area(5,10)
# You can see the output that __name__ is now area, because the entry point of this code is from the module "area"
I am in caller.py __name__: area
25.0
WHAT ARE EXCEPTIONS? - Exceptions are errors detected at the time of program execution
'''
Let's say you're driving a car on a road going to some destination, and you reached it safely without any trouble.
Road clear - Executing program without any exception
But life will not be same, some unexpected blockage is on the route, so you took a detour. From this you can say that
Blockage - Exception
Detour - Handling Exception
'''
1/0
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Input In [104], in <cell line: 9>() 1 ''' 2 Let's say you're driving a car on a road going to some destination, and you reached it safely without any trouble. 3 Road clear - Executing program without any exception (...) 7 8 ''' ----> 9 1/0 ZeroDivisionError: division by zero
# We got ZeroDivisionError on above cell
# This is called program crash
# Lets see how to handle a exception
# CASE 1 - Exception occurs
x = input("Enter number1: ")
y = input("Enter number2: ")
try:
z = int(x) / int(y)
except ZeroDivisionError as e:
print("Division by zero exception")
z = None
print("Division is: ",z)
Enter number1: 1 Enter number2: 0 Division by zero exception Division is: None
# CASE 2 - Exception does not occurs
x = input("Enter number1: ")
y = input("Enter number2: ")
try:
z = int(x) / int(y)
except Exception as e:
print("Exception occured: ",e)
z = None
print("Division is: ",z)
Enter number1: 6 Enter number2: 2 Division is: 3.0
# CASE 3 - Multiple Exception handled
x = input("Enter number1: ")
y = input("Enter number2: ")
try:
z = x / int(y) # I did not mention the type which will throw error
except ZeroDivisionError as e:
print("Division by zero exception")
z = None
except TypeError as e:
print('Type error exception')
z = None
print("Division is: ",z)
Enter number1: 4 Enter number2: 2 Type error exception Division is: None
WHAT IS CLASS? - It applies not only to python but most of it since it falls under OOPS(Object Oriented Programming). A class is an abstraction of some entity which common sense of properties and matters.
WHAT IS OBJECT? - It is a specific instance of class. The behavior of class a varies. An object is created to work around that behavior.
# Class and object example
class Human:
def __init__(self, name, occupation): # __init__ will initialize the properties of that particular class
self.name = name # self keyword means the class itself its a mandatory syntax when you write class in python
self.occupation = occupation
def do_work(self):
if self.occupation == "cricket player":
print(self.name, "plays cricket")
elif self.occupation == "cameraman":
print(self.name, "take photos")
def speaks(self):
print(self.name, "says how are you?")
# creating instance 1
raj = Human("Rajesh Sharma","cameraman")
raj.do_work()
raj.speaks()
# creating instance 2
thala = Human("MS Dhoni","cricket player")
thala.do_work()
thala.speaks()
Rajesh Sharma take photos Rajesh Sharma says how are you? MS Dhoni plays cricket MS Dhoni says how are you?
# Lets see an example
class Vehicle:
def general_usage(self):
print("general use: transportation")
class Car(Vehicle): # Car inherits from Vehicle
def __init__(self): # __init__ is seen as a constructor
print("I am a car")
self.wheels = 4
self.has_roof = True
def specific_usage(self):
print("specific use: commute to work, vacation with family")
class MotorCycle(Vehicle): # MotorCycle inherits from Vehicle
def __init__(self):
print("I am a motor cycle")
self.wheels = 2
self.has_roof = False
def specific_usage(self):
print("specific use: road trip, racing")
# Creating instance for Car
c = Car()
c.general_usage() # This function comes from Vehicle class
c.specific_usage()
# Creating instance for MotorCycle
m = MotorCycle()
m.general_usage()
m.specific_usage()
'''
BENIFITS OF INHERITANCE:
-----------------------
1. Code Reuse
2. Extensibility
3. Readability
'''
I am a car general use: transportation specific use: commute to work, vacation with family I am a motor cycle general use: transportation specific use: road trip, racing
# isinstance and issubclass methods
print(isinstance(c,Car)) # It will show True because c is an instance for Car
print(isinstance(c,MotorCycle)) # It will show False because c is not an instance for MotorCycle
print(issubclass(Car,Vehicle)) # It will show True since Car is subclass of Vehicle
print(issubclass(Car,MotorCycle)) # It will show False since Car is not a subclass of MotorCycle
True False True False
# In this we will inherit a class from two different classes
class Father():
def gardening(self):
print("I do gardening")
class Mother():
def cooking(self):
print("I do cooking")
class Child(Father,Mother):
def sports(self):
print("I do sports")
# Creating Instance
c = Child()
c.gardening()
c.cooking()
c.sports()
I do gardening I do cooking I do sports
# Raise Standard Exception
try:
raise MemoryError('memory error') # MemoryError is built-in exception in python
except MemoryError as e:
print(e)
# You can use both generic and specific exceptions
# NOTE : User defined exceptions are always derived from Exception base class
memory error
# Exception in a class
class Accident(Exception):
def __init__(self,msg):
self.msg = msg
def handle(self):
print("accident occured. take detour")
try:
raise Accident('crash between two cars')
except Accident as e:
e.handle()
accident occured. take detour
FINALLY STATEMENT - People use it to do cleanup, lets say we have a function, it opens a file and you have code block with potential of exception happening, but you have the exception unhandled if its an unknown, thats where we use finally statement, it will always execute code in finally block no matter what
# Lets see that in a program
def process_file():
try:
f = open("data.txt")
x = 1/0
except FileNotFoundError as e:
print("inside except")
finally:
print("cleaning up file")
f.close()
process_file() # It will throw exception, but still executes the finally block
cleaning up file
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) Input In [129], in <cell line: 12>() 9 print("cleaning up file") 10 f.close() ---> 12 process_file() Input In [129], in process_file() 3 try: 4 f = open("data.txt") ----> 5 x = 1/0 6 except FileNotFoundError as e: 7 print("inside except") ZeroDivisionError: division by zero
What are iterators? - An object that contains a countable number of values
# Lets see an example
a = ["hey","bro","you'r","awesome"]
for i in a:
print(i)
# Internally the loop goes through elements one by one using __iter__ built-in method
hey bro you'r awesome
itr = iter(a)
itr
<list_iterator at 0x27ac691d3d0>
next(itr)
'hey'
next(itr)
'bro'
next(itr)
"you'r"
next(itr)
'awesome'
next(itr) # Now it will throw an error that the iteration stopped
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Input In [137], in <cell line: 1>() ----> 1 next(itr) StopIteration:
# Reverse iterator
itr = reversed(a) # It is also an built-in function
next(itr)
'awesome'
next(itr)
"you'r"
next(itr)
'bro'
next(itr)
'hey'
SAMPLE PROGRAM : Implement Remote Control Class that allows you to press "next" button to go to next TV channel
class RemoteControl():
def __init__(self):
self.channels = ["SunTV","VijayTV","KTV","Polimer"]
self.index = -1 # I want to come to first to first channel, so while iterating -1+1 becomes 0 which is first index
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index == len(self.channels):
raise StopIteration # You would have seen this on previous cells
return self.channels[self.index] # it means channels[0].....channels[n]
# Creating Instance for our iterator
r = RemoteControl()
itr = iter(r)
print(next(itr))
print(next(itr))
print(next(itr))
print(next(itr))
# YOU CAN ALSO FOR LOOP TO PRINT THESE ITERATIONS WHILE PRINTING BUT ITS A TEDIOUS PROCESS TO KEEP IN MIND OF THE LENGTH AND NUMBER OF ITERATIONS
SunTV VijayTV KTV Polimer
It is a simple way of creating iterators. These are the functions that return the traversal object.
def remote_control_next():
yield 'VijayTV' # yield is used to preserve the state and resume execution when a successive function is called unlike return statement which completely destroys the state
yield 'KTV'
itr = remote_control_next()
itr # It will show that its a generator object
'''Using this will conserve memory since you are not calling all of the elements in one shot'''
<generator object remote_control_next at 0x0000027AC74AC890>
next(itr)
'VijayTV'
next(itr)
'KTV'
for c in remote_control_next():
print(c)
VijayTV KTV
# SAMPLE PROGRAM - FIBONACCI SEQUENCE USING GENERATOR
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b # a becomes b and b becomes a+b
for f in fib():
if f > 50: # keeping a limit since the looping should not go infinite
break
print(f)
'''
BENIFITS OF USING GENERATOR OVER CLASS BASED ITERATOR
1. You dont need to define iter() and next() methods
2. You dont need to raise StopIteration exception. It automatically raises it.
'''
0 1 1 2 3 5 8 13 21 34
'\nBENIFITS OF USING GENERATOR OVER CLASS BASED ITERATOR\n1. You dont need to define iter() and next() methods\n2. You dont need to raise StopIteration exception. It automatically raises it.\n'
List/Set/Dict comprehensions provides a way to transform one list/set/dict into another
# LIST COMPREHENSION
# traditional way
numbers = [1,2,3,4,5,6,7]
even = []
for i in numbers:
if i%2 == 0:
even.append(i)
even
[2, 4, 6]
# Using list comprehension
even = [i for i in numbers if i%2 ==0]
even
'''
CODE BREAKDOWN
--------------
even = [i(output variable) for i in numbers(for loop) if i%2 ==0(condition)]
'''
[2, 4, 6]
# SQUARE OF NUMBERS
sqr_numbers = [i*i for i in numbers]
sqr_numbers
[1, 4, 9, 16, 25, 36, 49]
# SET COMPREHENSION
# Set is an unordered collection of unique items
s = set([1,2,3,4,5,2,3])
s # It will clean the duplicates
{1, 2, 3, 4, 5}
even = {i for i in s if i%2 == 0}
even
{2, 4}
# DICT COMPREHENSIONS
# Two lists
cities = ["mumbai","newyork","paris"]
countries = ["india","usa","france"]
# Traditional way
z = zip(cities, countries) # zip() is used to zip two lists together
z
<zip at 0x27ac7374840>
for a in z:
print(a)
('mumbai', 'india')
('newyork', 'usa')
('paris', 'france')
# Using dict comprehension
d = {city:country for city, country in zip(cities,countries)}
d
{'mumbai': 'india', 'newyork': 'usa', 'paris': 'france'}
A set is an unordered collection of unique elements
basket = {"orange","apple","mango","apple","orange"}
type(basket)
set
basket # It will remove duplicates
{'apple', 'mango', 'orange'}
# Another way to initialize set
a = set()
a.add(1)
a.add(2)
a.add(3)
a
{1, 2, 3}
# Since it is unordered we cannot do index operations
# We can use set to remove duplicates of a list but then the type changes
numbers = [1,2,3,4,2,3,4]
unique_numbers = set(numbers)
unique_numbers
{1, 2, 3, 4}
# Frozen set - You cannot change content
fs = frozenset(numbers)
fs
frozenset({1, 2, 3, 4})
# Frozenset does not allow to add new element, it will throw an error
fs.add(5)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Input In [168], in <cell line: 2>() 1 # Frozenset does not allow to add new element, it will throw an error ----> 2 fs.add(5) AttributeError: 'frozenset' object has no attribute 'add'
# Basic operations in set
x = {"a","b","c"}
"a" in x
True
"g" in x
False
for i in x:
print(i)
c b a
y = {"c", "d", "e"}
y
{'c', 'd', 'e'}
# Sets has union and intersection operations
x | y # All elements without duplicates
{'a', 'b', 'c', 'd', 'e'}
x & y # common elements between two sets
{'c'}
x - y # It will subtract common elements
{'a', 'b'}
x > y
False
x < y
False
x = {'d','e'}
x < y
True
If you run it on cmd, nothing happens, because it has nothing, it just initializing the parser and trying to pass the arguments
Now lets pass some real arguments. There are two kind of arguments,
# Positional argument
'''
Here we are writing a program that makes 3 inputs,
1. First Number
2. Second Number
3. Operation("add","subtract","multiply")
It should return result of operation based on inputs
'''
# Now we will parse the arguments
# Now we will write code for operations and execute it
# Optional arguments
# To make argument optional we just add -- in front of argument name
# While executing using optional arguments, the order does not matter
# We can also skip arguments unlike positional arguments
It is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure
# Lets take a example how we decorate in traditional way
import time
def calc_square(numbers):
start = time.time()
result = []
for number in numbers:
result.append(number*number)
end = time.time()
print("calc_square took " + str((end-start)*1000) + " milliseconds")
return result
def calc_cube(numbers):
start = time.time()
result = []
for number in numbers:
result.append(number*number*number)
end = time.time()
print("calc_cube took " + str((end-start)*1000) + " milliseconds")
return result
array = range(1,100000)
out_square = calc_square(array)
out_cube = calc_cube(array)
'''
The problem with this code is that, lets say you have a complex software project and you have written 200 functions
so in order to measure performance of all those 200 functions, you have to write start and end time to each of it.
And the actual functions logic and performance logic is combined in same function. It makes it less readable
'''.
calc_square took 7.998466491699219 milliseconds calc_cube took 12.560129165649414 milliseconds
# We will use decorators which allow us to wrap the function in another function
def time_it(func):
'''
Functions are first class objects in python. What it means is that they can be treated just like
any other variable and you can pass them as argument to another function or even return them as
a return value
'''
def wrapper(*args, **kwargs): # *args - positional arguments, **kwargs - keyword arguments
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__ + "took " + str((end-start)*1000) + " milliseconds")
return result
return wrapper
@time_it # way for using decorators
def calc_square(numbers):
result = []
for number in numbers:
result.append(number*number)
return result
@time_it
def calc_cube(numbers):
result = []
for number in numbers:
result.append(number*number*number)
return result
array = range(1,100000)
out_square = calc_square(array)
out_cube = calc_cube(array)
calc_squaretook 9.179353713989258 milliseconds calc_cubetook 10.071277618408203 milliseconds
It is used for working with arrays. It gives support for large, multi-dimensional arrays and matrices, along with a large collection of high-level mathematical functions to operate on these arrays.
import numpy as np
# One dimensional array
a = np.array([5,6,9])
a[0]
5
a[1]
6
a = np.array([[1,2],[3,4],[5,6]])
a.ndim # ndim is used to see dimensions
2
a .itemsize
4
# To change the datatype of the numpy array dtype argument is used
a = np.array([[1,2],[3,4],[5,6]], dtype=np.float64)
a.itemsize
8
a.size # total number of elements
6
a.shape # information on dimensions [rows and columns]
a
array([[1., 2.],
[3., 4.],
[5., 6.]])
a = np.array([[1,2],[3,4],[5,6]], dtype=np.complex128)
a
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j],
[5.+0.j, 6.+0.j]])
# Initializing array with placeholder numbers
np.zeros((3,4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
np.ones((3,4))
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
l = range(5)
l # creating list with range of 5
range(0, 5)
l[0]
0
l[1]
1
np.arange(1,5)
array([1, 2, 3, 4])
np.arange(1,5,2) # start,stop,step
array([1, 3])
np.linspace(1,5,10) # it will generate 10 numbers with linearly spaced
array([1. , 1.44444444, 1.88888889, 2.33333333, 2.77777778,
3.22222222, 3.66666667, 4.11111111, 4.55555556, 5. ])
np.linspace(1,5,5)
array([1., 2., 3., 4., 5.])
print(a.shape)
a
(3, 2)
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j],
[5.+0.j, 6.+0.j]])
a.reshape(2,3)
array([[1.+0.j, 2.+0.j, 3.+0.j],
[4.+0.j, 5.+0.j, 6.+0.j]])
a.reshape(6,1)
array([[1.+0.j],
[2.+0.j],
[3.+0.j],
[4.+0.j],
[5.+0.j],
[6.+0.j]])
print(a.ravel()) # to make it flat and make it 1D. It does not touch original array
a
[1.+0.j 2.+0.j 3.+0.j 4.+0.j 5.+0.j 6.+0.j]
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j],
[5.+0.j, 6.+0.j]])
a.min()
(1+0j)
a.max()
(6+0j)
a.sum()
(21+0j)
a.sum(axis=0) # vertical axis 0, horizontal axis 1
array([ 9.+0.j, 12.+0.j])
a.sum(axis=1)
array([ 3.+0.j, 7.+0.j, 11.+0.j])
np.sqrt(a) # compute square root of each element
array([[1. +0.j, 1.41421356+0.j],
[1.73205081+0.j, 2. +0.j],
[2.23606798+0.j, 2.44948974+0.j]])
np.std(a) # compute standard deviation
1.707825127659933
a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
a
array([[1, 2],
[3, 4]])
b
array([[5, 6],
[7, 8]])
a + b
array([[ 6, 8],
[10, 12]])
a - b
array([[-4, -4],
[-4, -4]])
a * b
array([[ 5, 12],
[21, 32]])
a / b
array([[0.2 , 0.33333333],
[0.42857143, 0.5 ]])
a.dot(b) # matrix product
array([[19, 22],
[43, 50]])
'''
3 main benifits of numpy array over a python list,
1. Less Memory
2. Fast
3. Convinient
'''
# lets compare how it has less memory
import numpy as np
import sys
l = range(1000)
print(sys.getsizeof(5)*len(l))
array = np.arange(1000)
print(array.size*array.itemsize)
# From the output we can see that List took 28 bytes of memory size while numpy array took only 4 bytes
28000 4000
# Now lets see how numpy array is fast and convinient
import time
SIZE = 10000000
l1 = range(SIZE)
l2 = range(SIZE)
a1 = np.arange(SIZE)
a2 = np.arange(SIZE)
start = time.time()
result = [(x+y) for x,y in zip(l1,l2)]
print("python list took: ",(time.time()-start)*1000)
start = time.time()
result = a1 + a2 # it is convinient, does not need to use list comprehension
print("numpy took: ",(time.time()-start)*1000)
# list took 773 milliseconds while numpy array only took 72 milliseconds -_-
python list took: 773.7343311309814 numpy took: 72.00193405151367
# Indexing and slicing
n = [6,7,8]
n[0:2] # slicing in normal list
[6, 7]
c = np.array([6,7,8])
c[0:2] # same concept can be used in numpy arrays also
array([6, 7])
a
array([[1, 2],
[3, 4]])
a = np.array([[6,7,8],[1,2,3],[9,3,2]])
a[1,2]
3
a[0:2, 2] # 0 to 1st row and then to second element
array([8, 3])
for row in a:
print(row)
[6 7 8] [1 2 3] [9 3 2]
for cell in a.flat: # flatten it as 1D array
print(cell)
6 7 8 1 2 3 9 3 2
a = np.arange(6).reshape(3,2)
b = np.arange(6,12).reshape(3,2)
a
array([[0, 1],
[2, 3],
[4, 5]])
b
array([[ 6, 7],
[ 8, 9],
[10, 11]])
np.vstack((a,b)) # we can stack two arrays vertically
array([[ 0, 1],
[ 2, 3],
[ 4, 5],
[ 6, 7],
[ 8, 9],
[10, 11]])
np.hstack((a,b)) # we can stack two arrays horizontally
array([[ 0, 1, 6, 7],
[ 2, 3, 8, 9],
[ 4, 5, 10, 11]])
a = np.arange(30).reshape(2,15)
a
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
r = np.hsplit(a,3) # we are splitting horizontally into 3
r[0]
array([[ 0, 1, 2, 3, 4],
[15, 16, 17, 18, 19]])
r[1]
array([[ 5, 6, 7, 8, 9],
[20, 21, 22, 23, 24]])
r[2]
array([[10, 11, 12, 13, 14],
[25, 26, 27, 28, 29]])
v = np.vsplit(a,2)
v[0]
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]])
v[1]
array([[15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
a = np.arange(12).reshape(3,4)
a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
b = a > 4 # its gonna store the result in array form
b
array([[False, False, False, False],
[False, True, True, True],
[ True, True, True, True]])
a[b] # index of an array is array itself, wherever it found true it return only those values
array([ 5, 6, 7, 8, 9, 10, 11])
a[b] = -1 # any element greater than 4 it replaced as -1
a
array([[ 0, 1, 2, 3],
[ 4, -1, -1, -1],
[-1, -1, -1, -1]])
# iterating numpy arrays
a = np.arange(12).reshape(3,4)
a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
for cell in a.flatten():
print(cell)
0 1 2 3 4 5 6 7 8 9 10 11
# nditer - Efficient multi dimensional iterator object to iterate over arrays
for x in np.nditer(a, order='F'): # fortran order
print(x)
0 4 8 1 5 9 2 6 10 3 7 11
# so many properties are given in original python documentation
# python is not about knowing every single thing
# Its about you knowing where to check for what
'''
We will look into,
1. Create a new file and write to it
2. Reading file line by line
3. File open modes
4. With statement
'''
f = open("sample.txt","w") # r , w , r+ , w+ , a
f.write("Howdy mates")
f.close()
# to append a line to a file
f = open("sample.txt","a")
f.write("\nHow are you doing?")
f.close()
# I have created a file called dbms and saved it to directory
# We will read the file line by line and aslo count the number of words in each line and append the word count to end of line
f = open("dbms.txt","r")
print(f.read())
f.close()
Differentiate between 1NF 2NF 3NF BCNF 4NF 5NF (Write any seven points)
# Now appending the count
f = open("dbms.txt","r")
f_out = open("sample_out.txt","w") # creating output file
for line in f:
tokens = line.split(' ')
# split() will split the string using input character and return a list (array) of words
f_out.write("word count:"+str(len(tokens)) + " " + line)
f.close()
f_out.close()
'''
FILE OPEN MODES:
---------------
r - Opens file for reading only. Throws error if file does not exist
w - Opens file for writing only. If file doesnt exist then it will create new one. If exist then it will overwrite it
r+ - Opens file for both reading and writing
w+ - Opens file for reading and writing. If file doesnt exist then it will create new one. If it exist then it will
overwrite it.
a - Opens a file in append mode. Whatever you write to file will get appended and original content will not be overwritten
'''
# with statement
# we dont need explicitly close the file everytime
# i.e. You dont need to do f.close() if you use with.
# Using with will automatically close the file for you
with open("sample.txt","r") as f:
print(f.read())
print(f.closed) # f.closed flag tells us if the file is closed or still open
# This is just to show the working of with, not mandatory to use f.closed
Howdy mates How are you doing? True
It enables CPUs to run different parts(threads) of a process concurrently to maximize CPU utilization
'''
SAMPLE PROGRAM
--------------
For a given list of numbers print square and cube of every numbers
For example,
Input : [2,3,8,9]
Output : Square list - [4,9,64,81]
Cube list - [8,27,512,729]
'''
# Lets frame it in a program in traditional way
import time
def calc_square(numbers):
print("Calculate square numbers")
for n in numbers:
time.sleep(0.2) # keeping CPU idle
print('square: ',n*n)
def calc_cube(numbers):
print("Calculate cube numbers")
for n in numbers:
time.sleep(0.2)
print('cube: ',n*n*n)
arr = [2,3,8,9]
t = time.time()
calc_square(arr)
calc_cube(arr)
print("done in: ",time.time()-t)
print("Hah... I am done with all my work now!")
Calculate square numbers square: 4 square: 9 square: 64 square: 81 Calculate cube numbers cube: 8 cube: 27 cube: 512 cube: 729 done in: 1.6621217727661133 Hah... I am done with all my work now!
# Using multithreading we can decrease amount of time it takes
import time
import threading
def calc_square(numbers):
print("Calculate square numbers")
for n in numbers:
time.sleep(0.2) # keeping CPU idle
# This is an artificial delay I made, there some cases like web services, where delay happens
print('square: ',n*n)
def calc_cube(numbers):
print("Calculate cube numbers")
for n in numbers:
time.sleep(0.2)
print('cube: ',n*n*n)
arr = [2,3,8,9]
t = time.time()
t1 = threading.Thread(target=calc_square, args=(arr,)) # We can also parse multiple arguments
t2 = threading.Thread(target=calc_cube, args=(arr,))
t1.start()
t2.start()
t1.join() # join() will make the thread to wait until another thread does its job
t2.join()
print("done in: ",time.time()-t)
print("Hah... I am done with all my work now!")
# You can compare both traditional way and using multithreading
# It gets decreased from 1.6s ---> 0.8s, its literally decreased half the time
Calculate square numbersCalculate cube numbers square: 4 cube: 8 square: 9 cube: 27 square: 64 cube: 512 square: 81 cube: 729 done in: 0.8565447330474854 Hah... I am done with all my work now!
It is a package that supports spawning processes using an API similar to the threading module.
'''
Lets create two processes,
1. First is to calculate square of all numbers
2. Second one is to calculate cube of numbers
Note: Functionality within this package requires that the __main__ method be importable by the children.
This is covered in Programming guidelines however it is worth pointing out here.
This means that some examples, such as the multiprocessing.Pool examples will not work in the interactive interpreter.
So I will vs code and share screenshot and the code is in the documentation
import multiprocessing
square_result = []
cube_result = []
def calc_square_ml(numbers):
global square_result # it recognized as global variable
for n in numbers:
print('square: ' + str(n*n))
square_result.append(n*n)
print('within a process: result ' + str(square_result))
def calc_cube_ml(numbers):
global cube_result
for n in numbers:
print('cube: ' + str(n*n*n))
cube_result.append(n*n*n)
print('within a process: result ' + str(cube_result))
if __name__ == "__main__":
arr = [2,3,8,9]
p1 = multiprocessing.Process(target=calc_square_ml, args = (arr,))
p2 = multiprocessing.Process(target=calc_cube_ml, args = (arr,))
p1.start()
p2.start()
p1.join()
p2.join()
print("Done!")
'''
print("CHECK ORIGINAL PYTHON DOCUMENTATION FOR MORE DETAILS")
CHECK ORIGINAL PYTHON DOCUMENTATION FOR MORE DETAILS
'''
import multiprocessing
result = []
def calc_square(numbers):
global result
for n in numbers:
result.append(n*n)
print('inside process ' + str(result))
if __name__ == "__main__":
numbers = [2,3,5]
p = multiprocessing.Process(target=calc_square, args=(numbers,))
p.start()
p.join()
print('outside process ' + str(result))
If we see this code, it gives output like inside process has the values but not the outside process,
Lets see why, whenever a new process is created, the new process get its own address space, a address space is
the place where it stores all variables etc.
'''
print("Look at below image")
Look at below image
As in the diagram, left side is the global variable memory present in main, while its copy is in child program, this is a problem, since it cant be changed as it is.
But there is way to share memory which lives outside process
'''
There are two ways to share memory in multiprocessing,
1. Using array
2. Using value
'''
print("Look at the image")
Look at the image
# Using Array
'''
import multiprocessing
def calc_square(numbers):
for idx, n in enumerate(numbers):
result[idx] = n*n
if __name__ == "__main__":
numbers = [2,3,5]
result = multiprocessing.Array('i,3') # 'd' means double, 'i' means integer
p = multiprocessing.Process(target=calc_square, args=(numbers,))
p.start()
p.join()
print(" Result: ",result[:]) # ":" - way to print all elements in array
'''
print("Look below for vs code and output")
Look below for vs code and output
# Using Value
'''
import multiprocessing
def calc_square(numbers, v):
v.value = 5.67
if __name__ == "__main__":
numbers = [2,3,5]
v = multiprocessing.Value('d', 0.0)
p = multiprocessing.Process(target=calc_square, args=(numbers, v))
p.start()
p.join()
print(v.value)
child process is updating values and still parent process able to access it
'''
print("Look below cell")
Look below cell
P1 and P2 has own address space whenever they need to share data they use shared memory. Queue basically works on shared memory. So lets see how it used in program
'''
import multiprocessing
def calc_square(numbers, q):
for n in numbers:
q.put(n*n) # FIFO concept - insert data at end and pull from end
if __name__ == "__main__":
numbers = [2,3,5]
q = multiprocessing.Queue()
p = multiprocessing.Process(target=calc_square, args=(numbers, q))
p.start()
p.join()
while q.empty() is False:
print(q.get())
Queue is used to share data within two processes(parent, child).
D/B Queue module and multiprocessing queue module
-------------------------------------------
-----------------------------------------|-------------------------------------------|
Multiprocessing Queue | Queue |
-----------------------------------------|-------------------------------------------|
import multiprocessing | import queue |
q = multiprocessing.Queue() | q = queue.Queue() |
-----------------------------------------|-------------------------------------------|
Lives in shared memory | Lives in in-process memory |
-----------------------------------------|-------------------------------------------|
Used to share data between processes | Used to share data between threads |
-----------------------------------------|-------------------------------------------|
'''
print("Look below for vs code output")
Look below for vs code output
# In real life why we need lock?
# There are some resources where two people cant use it at the same time, like Bathroom.
# It would be pretty embarassing if two people use it at the same time.
# So in programming world, when two process try access shared resource, it can create problem, we need to protect it with lock
If we look at the code and output, It prints 200 on first run, and it printed 185 on second run. It is because when one process runs, another processes also runs using the same resource which makes inappropriate and inconsistent results. This is why we use locks
'''
CODE WITH LOCK APPLIED
----------------------
import time
import multiprocessing
def deposit(balance, lock):
for i in range(100):
time.sleep(0.1)
lock.acquire()
balance.value = balance.value + 1
lock.release()
# we put acquire and release only on critical section which is the logic of the code
def withdraw(balance, lock):
for i in range(100):
time.sleep(0.1)
lock.acquire()
balance.value = balance.value - 1
lock.release()
if __name__ == '__main__':
balance = multiprocessing.Value('i', 200)
lock = multiprocessing.Lock()
d = multiprocessing.Process(target=deposit,args=(balance,lock))
w = multiprocessing.Process(target=withdraw,args=(balance,lock))
d.start()
w.start()
d.join()
w.join()
print(balance.value)
'''
print("Look below for output")
Look below for output
So now, if you see, after using lock, it will give out same result, even if you run it multiple times
# If we see the below program there's nothing wrong in there. But we'll how it internally works
The cores are processing unit, here we have 4 cores, when we run a program the OS gonna select one core. But other cores are sitting idle. But if we do much more complex and heavy, then it makes it difficult for one core to handle it, so we will divide the work.
PARALLEL PROCESSING
# lETS SEE A CODE EXAMPLE
'''
from multiprocessing import Pool
def f(n):
return n*n
if __name__ == "__main__":
array = [1,2,3,4,5]
p = Pool()
result = p.map(f,array) # It will divide the work to respective cores
print(result)
'''
print("Look at the vs code and output")
Look at the vs code and output
Visually it has no difference, but internally it did divide the work, Lets see the time performance to get an understanding
'''
from multiprocessing import Pool
import time
def f(n):
sum = 0
for x in range(1000):
sum += x*x
return sum
if __name__ == "__main__":
t1 = time.time()
p = Pool()
result = p.map(f,range(100000)) # It will divide the work to respective cores
p.close()
p.join()
print("Pool took:",time.time()-t1)
t2 = time.time()
result = []
for x in range(100000):
result.append(f(x))
print("Serial processing took:",time.time()-t2)
'''
print("Look at the vs code and output")
Look at the vs code and output
Pool only took 5s, where normal serial processing took 16s.
PYTHON TESTING FRAMEWORKS
Among these 3, pytest is the best
# Lets see an example
'''
I already created a module named math_t.py. Mentioned the code in documentation
def calc_total(a,b):
return a+b
def calc_multiply(a,b):
return a*b
'''
print("After creating module, we need to create a unit test module")
After creating module, we need to create a unit test module
# test_math.py
import math_t
def test_calc_total():
total = math_t.calc_total(4,5)
assert total == 9
def test_calc_multiply():
result = math_t.calc_multiply(10,3)
assert result == 30
# There are two ways to do unit testing in python
# First one is using command "python -m pytest", It will search for files that has prefix "test_" and run the tests for it
# Second one is using command "py.test"
PYTEST: PARAMETERIZED TESTS We can combine multiple test cases into a single test case
# In normal unit tests, if we want to write multiple tests, we will write it like this
'''
import math_t
def test_calc_square_1():
total = math_t.calc_square(4)
assert total == 16
def test_calc_square_2():
result = math_t.calc_square(3)
assert result == 9
def test_calc_square_3():
result = math_t.calc_square(6)
assert result == 36
But there is a problem with this, we cant write 100's of test cases like this, so thats where parameterized tests
comes into the play
'''
print("Lets look further")
Lets look further
# We can write it like this
import math_t
import pytest
@pytest.mark.parametrize("test_input, expected_output", # We used pytest decorator and passed parameters - i/p, o/p, tuple
[
(4, 16),
(3, 9),
(6, 36)
]
)
def test_calc_square_1(test_input, expected_output):
total = math_t.calc_square(test_input)
assert total == expected_output
# AND THAT'S THE END OF IT
# STILL PYTHON HAS LOT OF LIBRARIES AND MICRO MODULES
# PYTHON IS ALL ABOUT IMPLYING IT ON SOMETHING. DO THAT RIGHT, ITS GONNA BE AN EASY JOURNEY